#include "SbotIde.hpp"
#include <QDir>
#include <QFile>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QPluginLoader>
#include <QtQuickControls2/QQuickStyle>

SbotIde*       SbotIde::instance = nullptr;
std::once_flag SbotIde::initInstanceFlag;

SbotIde::SbotIde(QObject* parent) : QObject(parent) {}

void SbotIde::initInstance()
{
    instance = new SbotIde();
}

void SbotIde::run(int argc, char* argv[])
{
    const QUrl      url(QStringLiteral(MAIN_WINDOWS_QML_FILE_PATH));
    QGuiApplication app(argc, argv);

    /* Clean last cache. */
    QDir cacheDir(QCoreApplication::applicationDirPath() + IDE_CACHE_PATH);
    if(cacheDir.exists())
    {
        cacheDir.removeRecursively();
    }

    m_engine = new QQmlApplicationEngine(nullptr);  // Create the QML engine.

    QQuickStyle::setStyle("Basic");  // Set the base theme style.

    connect(
        m_engine, &QQmlApplicationEngine::objectCreated, QCoreApplication::instance(),
        [url](QObject* obj, const QUrl& objUrl) {
            if(!obj && url == objUrl)
            {
                QCoreApplication::exit(-1);  // Exit the application if the main QML file can't be loaded.
            }
        },
        Qt::QueuedConnection);

    QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &SbotIde::aboutToQuit);

    m_engine->load(url);                   // Load the main QML file.
    loadIdeConfig();                       // Load the IDE configuration.
    detectPlugins(PLUGIN_PATH);            // Refresh the plugins.
    QCoreApplication::instance()->exec();  // Start the application's event loop.
}

QQuickItem* SbotIde::createQuickItem(const QString& filePath)
{
    QQuickItem* item = nullptr;

    m_engine->clearComponentCache();  // Clear the component cache to avoid memory leaks.

    if(m_engine)
    {
        QQmlComponent* component = new QQmlComponent(m_engine, QUrl(filePath), this);
        if(component->isError())
        {
            qDebug() << component->errors();
            SBT_MSG_E("Create QuickItem error: " + filePath);
            goto _end;
        }

        QObject* obj = component->create();
        if(!obj)
        {
            goto _end;
        }

        item = qobject_cast< QQuickItem* >(obj);
        if(!item)
        {
            obj->deleteLater();
            goto _end;
        }
    }

_end:
    return item;
}

void SbotIde::showMessage(const QString& title, const QString& message)
{
    QObject* mainWindow = qobject_cast< QObject* >(m_engine->rootObjects().first());

    if(mainWindow)
    {
        QObject* globalMsgDialog = mainWindow->findChild< QObject* >("globalMsgDialog");

        if(globalMsgDialog)
        {
            QMetaObject::invokeMethod(globalMsgDialog, "showMsgDialog", Q_ARG(QVariant, title), Q_ARG(QVariant, message));
        }
    }
}

SbotPlugin* SbotIde::parseSbotPlugin(const QString& filePath)
{
    QDir cacheDir(QCoreApplication::applicationDirPath() + IDE_CACHE_PATH);
    if(!cacheDir.exists())
    {
        cacheDir.mkpath(".");
    }

    QString tempFileName = QFileInfo(filePath).baseName() + "_" + QUuid::createUuid().toString(QUuid::WithoutBraces) + ".dll";
    QString tempFilePath = cacheDir.absoluteFilePath(tempFileName);

    if(!QFile::copy(filePath, tempFilePath))
    {
        SBT_MSG_E(QString("无法复制插件到临时目录: %1").arg(tempFilePath));
        return nullptr;
    }

    QPluginLoader* pluginLoader = new QPluginLoader(tempFilePath);
    SbotPlugin*    plugin       = qobject_cast< SbotPlugin* >(pluginLoader->instance());

    if(plugin)
    {
        plugin->pluginLoader   = pluginLoader;
        plugin->pluginFilePath = filePath;
        plugin->pluginInfo     = pluginLoader->metaData();
    }
    else
    {
        pluginLoader->unload();
        SBT_MSG_E(pluginLoader->errorString());
        delete pluginLoader;
        return nullptr;
    }

    return plugin;
}

void SbotIde::refreshPluginsListView()
{
    static QObject* pluginList = nullptr;

    if(!pluginList)
    {
        QObject* mainWindow = qobject_cast< QObject* >(m_engine->rootObjects().first());
        pluginList          = mainWindow->findChild< QObject* >("pluginList");
    }

    if(pluginList)
    {
        QMetaObject::invokeMethod(pluginList, "clearElement");

        for(int icnt = 0; icnt < m_plugins.size(); icnt++)
        {
            QVariantMap newPluginElement;
            newPluginElement["icon"] = m_plugins[icnt]->pluginLogo();
            QMetaObject::invokeMethod(pluginList, "addElement", Q_ARG(QVariant, QVariant::fromValue(newPluginElement)));
        }
    }
}

SbotIde& SbotIde::getInstance()
{
    std::call_once(initInstanceFlag, &SbotIde::initInstance);
    return *instance;
}

void SbotIde::detectPlugins(const QString& path)
{
    QDir              pluginsDir(path);
    const QStringList entries = pluginsDir.entryList(QStringList() << "*.dll", QDir::Files);

    for(const QString& fileName : entries)
    {
        loadPlugin(path + fileName);
    }
}

void SbotIde::loadPlugin(const QString& filePath)
{
    SbotPlugin* plugin = parseSbotPlugin(filePath);

    if(plugin)
    {
        m_plugins.append(plugin);
        refreshPluginsListView();
    }
}

void SbotIde::reloadPlugin(int index)
{
    SbotPlugin* plugin = m_plugins.at(index);

    if(plugin)
    {
        QString        pluginFilePath = plugin->pluginFilePath;
        QPluginLoader* pluginLoader   = plugin->pluginLoader;

        closePlugin(index);
        delete plugin;
        pluginLoader->unload();
        delete pluginLoader;
        plugin = parseSbotPlugin(pluginFilePath);

        if(plugin)
        {
            m_plugins.replace(index, plugin);
        }
        else
        {
            m_plugins.removeAt(index);
        }
        refreshPluginsListView();
    }
}

void SbotIde::removePlugin(int index)
{
    SbotPlugin* plugin = m_plugins.at(index);

    if(plugin)
    {
        QString        pluginFilePath = plugin->pluginFilePath;
        QPluginLoader* pluginLoader   = plugin->pluginLoader;

        closePlugin(index);
        delete plugin;
        m_plugins.removeAt(index);
        refreshPluginsListView();

        pluginLoader->unload();
        delete pluginLoader;
    }
}

void SbotIde::openPlugin(int index)
{
    static QQuickItem* pluginViewItem = nullptr;
    SbotPlugin*        plugin         = m_plugins.at(index);

    if(!pluginViewItem)
    {
        QObject* mainWindow = qobject_cast< QObject* >(m_engine->rootObjects().first());
        pluginViewItem      = mainWindow->findChild< QQuickItem* >("pluginView");
    }

    if(plugin && pluginViewItem)
    {
        if(!plugin->uiItem)
        {
            plugin->uiItem = createQuickItem(plugin->pluginQml());
        }

        /* We need close last function content view. */
        if(pluginViewItem->childItems().size() > 0)
        {
            pluginViewItem->childItems().first()->setParentItem(nullptr);
        }

        if(plugin->uiItem)
        {
            plugin->uiItem->setParentItem(pluginViewItem);
        }

        if(!plugin->isOpened())
        {
            plugin->open();
        }
    }
}

void SbotIde::closePlugin(int index)
{
    SbotPlugin* plugin = m_plugins.at(index);

    if(plugin && plugin->isOpened())
    {
        if(plugin->uiItem)
        {
            plugin->uiItem->deleteLater();
            plugin->uiItem = nullptr;
        }
        plugin->close();
    }
}

void SbotIde::setThemeManager(QObject* item)
{
    m_themeManager = item;
}

void SbotIde::loadIdeConfig()
{
    QJsonObject cfgJsonObject;
    QFile       cfgJsonFile("./sbotIde.cfg");

    if(!cfgJsonFile.open(QIODevice::ReadOnly))
    {
        SBT_MSG_E("Failed to open the ./sbotIde.cfg!");
        return;
    }

    QByteArray    jsonFileContent = cfgJsonFile.readAll();
    QJsonDocument cfgJsonDoc      = QJsonDocument::fromJson(jsonFileContent);
    cfgJsonFile.close();

    if(cfgJsonDoc.isNull())
    {
        qWarning() << "Failed to parse JSON.";
        return;
    }
    cfgJsonObject = cfgJsonDoc.object();

    if(cfgJsonObject.contains("theme") && m_themeManager)
    {
        QMetaObject::invokeMethod(m_themeManager, "setTheme", Q_ARG(QVariant, cfgJsonObject["theme"].toInt()));
    }
}

void SbotIde::saveIdeConfig()
{
    QJsonObject cfgJsonObject;
    QFile       cfgJsonFile(IDE_CONFIG_FILE_PATH);

    if(!cfgJsonFile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        SBT_MSG_E(QString("Failed to open the %1!").arg(IDE_CONFIG_FILE_PATH));
        return;
    }

    if(m_themeManager)
    {
        QVariant themeIndex = 0;
        QMetaObject::invokeMethod(m_themeManager, "currentTheme", Q_RETURN_ARG(QVariant, themeIndex));
        cfgJsonObject["theme"] = themeIndex.toInt();  // Theme identification.
    }

    QJsonDocument jsonDocument(cfgJsonObject);
    QString       jsonString = jsonDocument.toJson(QJsonDocument::Indented);
    cfgJsonFile.write(jsonString.toUtf8());
    cfgJsonFile.close();

    SBT_MSG_I("IDE config saved.");
}

void SbotIde::aboutToQuit()
{
    saveIdeConfig();  // Save IDE config before quit.

    /* Close all plugin. */
    for(auto plugin : m_plugins)
    {
        plugin->close();
    }
}